# -*- coding: utf-8 -*-
# @Time    : 2024/7/22 17:35
# @Author  : yujiahao
# @File    : 54_fastapi_jsonresponse_selft.py
# @description:自定义响应 - HTML，流，文件和其他


"""
FastAPI 默认会使用 JSONResponse 返回响应。

你可以通过直接返回 Response 来重载它，参见 直接返回响应。

但如果你直接返回 Response，返回数据不会自动转换，也不会自动生成文档（例如，在 HTTP 头 Content-Type 中包含特定的「媒体类型」作为生成的 OpenAPI 的一部分）。

你还可以在路径操作装饰器中声明你想用的 Response。

你从路径操作函数中返回的内容将被放在该 Response 中。

并且如果该 Response 有一个 JSON 媒体类型（application/json），比如使用 JSONResponse 或者 UJSONResponse 的时候，返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 response_model 自动转换（和过滤）。

如果你使用不带有任何媒体类型的响应类，FastAPI 认为你的响应没有任何内容，所以不会在生成的OpenAPI文档中记录响应格式。
"""

import uvicorn
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse, UJSONResponse
from starlette.responses import HTMLResponse, Response, PlainTextResponse, RedirectResponse, StreamingResponse, \
    FileResponse

app = FastAPI()


# todo 使用 ORJSONResponse

# 例如，如果你需要压榨性能，你可以安装并使用 orjson 并将响应设置为 ORJSONResponse。
# 导入你想要使用的 Response 类（子类）然后在 路径操作装饰器 中声明它。

@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
    """
    参数 response_class 也会用来定义响应的「媒体类型」。

    在这个例子中，HTTP 头的 Content-Type 会被设置成 application/json。

    并且在 OpenAPI 文档中也会这样记录。

    另外的，ORJSONResponse 目前只在 FastAPI 中可用，而在 Starlette 中不可用。
    """
    return ORJSONResponse([{"item_id": "Foo"}])


# todo  HTML 响应

# 使用 HTMLResponse 来从 FastAPI 中直接返回一个 HTML 响应。

# 导入 HTMLResponse。
# 将 HTMLResponse 作为你的 路径操作 的 response_class 参数传入。

@app.get("/items_1/", response_class=HTMLResponse)
async def read_items_1():
    """
    参数 response_class 也会用来定义响应的「媒体类型」。

    在这个例子中，HTTP 头的 Content-Type 会被设置成 text/html。

    并且在 OpenAPI 文档中也会这样记录。
    """
    return """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """


# todo 返回一个 Response
# 也可以通过直接返回响应在 路径操作 中直接重载响应。

# 和上面一样的例子，返回一个 HTMLResponse 看起来可能是这样：


@app.get("/items_2/")
async def read_items_2():
    """
    路径操作函数直接返回的 Response 不会被 OpenAPI 的文档记录（比如，Content-Type 不会被文档记录），并且在自动化交互文档中也是不可见的。

    当然，实际的 Content-Type 头，状态码等等，将来自于你返回的 Response 对象
    """
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)


# todo OpenAPI 中的文档和重载 Response

def generate_html_response():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)


@app.get("/items_3/", response_class=HTMLResponse)
async def read_items_3():
    """
    在这个例子中，函数 generate_html_response() 已经生成并返回 Response 对象而不是在 str 中返回 HTML。

    通过返回函数 generate_html_response() 的调用结果，你已经返回一个重载 FastAPI 默认行为的 Response 对象，

    但如果你在 response_class 中也传入了 HTMLResponse，FastAPI 会知道如何在 OpenAPI 和交互式文档中使用 text/html 将其文档化为 HTML。
    """

    return generate_html_response()


# todo 可用响应

# todo HTMLResponse

@app.get("/legacy/")
def get_legacy_data():
    """
    可用响应
    这里有一些可用的响应。

    要记得你可以使用 Response 来返回任何其他东西，甚至创建一个自定义的子类。

    技术细节：

    你也可以使用 from starlette.responses import HTMLResponse。

    FastAPI 提供了同 fastapi.responses 相同的 starlette.responses 只是为了方便开发者。但大多数可用的响应都直接来自 Starlette。

    Response
    其他全部的响应都继承自主类 Response。

    你可以直接返回它。

    Response 类接受如下参数：

    - content: 一个 str 或者 bytes。
    - status_code: 一个 int 类型的 HTTP 状态码。
    - headers: 一个由字符串组成的 dict。
    - media_type: 一个给出媒体类型的 str，比如 "text/html"。

    FastAPI（实际上是 Starlette）将自动包含 Content-Length 的头。它还将包含一个基于 media_type 的 Content-Type 头，并为文本类型附加一个字符集。
    """

    data = """<?xml version="1.0"?>
    <shampoo>
    <Header>
        Apply shampoo here.
    </Header>
    <Body>
        You'll have to use soap here.
    </Body>
    </shampoo>
    """
    return Response(content=data, media_type="application/xml")


# todo PlainTextResponse
# 接受文本或字节并返回纯文本响应
@app.get("/", response_class=PlainTextResponse)
async def main():
    return "Hello World"


# todo UJSONResponse
# UJSONResponse 是一个使用 ujson 的可选 JSON 响应。
# 在处理某些边缘情况时，ujson 不如 Python 的内置实现那么谨慎。
# ORJSONResponse 可能是一个更快的选择。
# UJSONResponse 是一种使用 ultrajson 库进行 JSON 序列化的响应类，比默认的 JSONResponse 更快。
@app.get("/items_4/", response_class=UJSONResponse)
async def read_items_4():
    return [{"item_id": "Foo"}]


# todo RedirectResponse
# 返回 HTTP 重定向。默认情况下使用 307 状态代码（临时重定向）。

@app.get("/typer")
async def redirect_typer():
    """
    RedirectResponse 是 FastAPI 和 Starlette 提供的一个响应类，用于返回重定向响应。
    重定向是一种 HTTP 响应，告诉客户端（通常是浏览器）应当访问另一个 URL。
    重定向通常用于处理页面迁移、路径更改、登录后的跳转等场景。

    使用场景
    RedirectResponse 适用于以下场景：

    - 页面迁移：当一个页面的路径发生变化时，可以使用重定向将旧路径的访问者引导到新路径。
    - 登录跳转：在用户登录成功后，将其重定向到用户主页或仪表盘。
    - 外部链接：将用户重定向到外部网站或资源。
    """
    return RedirectResponse("https://typer.tiangolo.com")


# todo StreamingResponse
# 采用异步生成器或普通生成器/迭代器，然后流式传输响应主体。

async def fake_video_streamer():
    for i in range(10):
        yield b"some fake video bytes"


@app.get("/main_1")
async def main_1():
    """
    StreamingResponse 是 FastAPI 和 Starlette 提供的一个响应类，用于返回流式响应。
    流式响应允许服务器逐块发送数据给客户端，而不是一次性发送所有数据。
    这在处理大文件、实时数据流、生成动态内容等场景中非常有用。

    使用场景
    `StreamingResponse 适用于以下场景：

      - 大文件传输：当需要传输大文件时，可以逐块发送数据，减少内存占用。
      - 实时数据流：例如实时视频流、音频流或其他需要持续发送数据的应用。
      - 动态生成内容：例如生成大型报告、日志文件等，可以逐块生成并发送给客户端。`
    """
    return StreamingResponse(fake_video_streamer())


# todo 对类似文件的对象使用 StreamingResponse
# 如果您有类似文件的对象（例如，由 open() 返回的对象），则可以在 StreamingResponse 中将其返回。
# 包括许多与云存储，视频处理等交互的库。

some_file_path = '../../../input/54_fastapi_test.mp4'


@app.get("/main_2")
def main_2():
    def iterfile():  # (1)
        # 注意在这里，因为我们使用的是不支持 async 和 await 的标准 open()，我们使用普通的 def 声明了路径操作。
        with open(some_file_path, mode="rb") as file_like:  # (2)
            yield from file_like  # (3)

    return StreamingResponse(iterfile(), media_type="video/mp4")


# todo FileResponse

"""
异步传输文件作为响应。

与其他响应类型相比，接受不同的参数集进行实例化：

- path: 要流式传输的文件的文件路径。
- headers: 任何自定义响应头，传入字典类型。
- media_type: 给出媒体类型的字符串。如果未设置，则文件名或路径将用于推断媒体类型。
- filename: 如果给出，它将包含在响应的 Content-Disposition 中。

文件响应将包含适当的 Content-Length，Last-Modified 和 ETag 的响应头。
"""


@app.get("/main_3")
async def main_3():
    return FileResponse(some_file_path)


if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)
